#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SIZE 1024
#define ARGS 64
#ifndef __MYSHELL_H__
#define __MYSHELL_H__
#endif
//命令行参数表
char *gargv[ARGS]={NULL};
int gargc =0;
char pwd[1024]; // 全局变量空间，保存当前shell进程的工作路径
int lastcode=0;
#define NONE_REDIR 0
#define OUPUT_REDIR 1
#define APPEND_REDIR 2
#define INPUT_REDIR 3
int redir_type = NONE_REDIR;
std::string filename;
void InitGlobal(){
filename.clear();
    redir_type = NONE_REDIR;
    gargc = 0;
    memset(gargv, 0, sizeof(gargv));//按照字节初始化
}
//限制函数作用域到当前文件，避免命名冲突
static std::string getuser()
{
  std::string username =getenv("USER");
  return username.empty()?"None":username;

}
static std::string gethostname()
{
  std::string hostname=getenv("HOSTNAME");
  return hostname.empty()?"None":hostname;
}
//static std::string getpwd()
//{
//  std::string pwd =getenv("PWD");
//  return pwd.empty()?"None":pwd;
//}
static std::string GetPwd()
{
    // 环境变量的变化，可能会依赖于进程,pwd需要shell自己更新环境变量的值
    //std::string pwd = getenv("PWD");
    //return pwd.empty() ? "None" : pwd;
    
    char temp[1024];
    getcwd(temp, sizeof(temp));//获取当前进程的工作路径
    // 顺便更新一下shell自己的环境变量pwd
    snprintf(pwd, sizeof(pwd), "PWD=%s", temp);
    putenv(pwd);

    // /
    // /home/whb/code/code
    std::string pwd_lable = temp;
    const std::string pathsep = "/";
    auto pos = pwd_lable.rfind(pathsep);
    if(pos == std::string::npos)
    {
        return "None";
    }

     pwd_lable = pwd_lable.substr(pos+pathsep.size());
    return pwd_lable.empty() ? "/" : pwd_lable;
}
//获取键盘输入的
bool GetCommandString(char cmd_str_buff[],int len)
{
  //获取标准输入fgets()
  if(cmd_str_buff==NULL || len <=0)
  {
    return false;
  }
  //char *fgets(char *str, int n, FILE *stream);
  //str：指向字符数组的指针，用于存储读取的数据 n：最大读取字符数（包括结尾的 \0）stream：输入流，如 stdin（标准输入）或文件指针。
  char* res =fgets(cmd_str_buff,len,stdin);//成功返回str的指针失败返回NULL
  if(res==NULL)
  {
    return false;
  }
  //ls -a -l \n -> ls -a -l \0
  cmd_str_buff[strlen(cmd_str_buff)-1]=0;//因为输入换行会导致字符串里面的多个\n
  return strlen(cmd_str_buff)==0?false:true;
}
void PrintCommandPrompt()
{
  std:: string user =getuser();
  std:: string hostname =gethostname();
  std:: string pwd =GetPwd();
  printf("[%s@%s %s]#",user.c_str(),hostname.c_str(),pwd.c_str());
//c_str返回字符串的形式
}
bool  ParseCommandString(char cmd[])//这里传的是数组名，数组名是一个指针，这边也用一个数组接受也是指针，所以是传值传参
{
  if(cmd==NULL)
  {
    return false;

  }
  //ls -a -l -> "ls" "-a" "-l"
#define space " "
  gargv[gargc++]=strtok(cmd,space);//无分割内容返回NULL
  while((bool)(gargv[gargc++]=strtok(NULL,space)));
  gargc--;//要回退一格
//#define DEBUG
#ifdef DEBUG
        printf("gargc: %d\n", gargc);
        printf("----------------------\n");
        for(int i = 0; i < gargc; i++)
        {
            printf("gargv[%d]: %s\n",i, gargv[i]);
        }
        printf("----------------------\n");
        for(int i = 0; gargv[i]; i++)
        {
            printf("gargv[%d]: %s\n",i, gargv[i]);
        }
#endif

  return true;

}
#define TrimSpace(start) do{\
        while(isspace(*start))\
        {\
            start++;\
        }\
    }while(0)

// "ls -a -l > log.txt" -> "ls -a -l\0log.txt"; 
// filename = log.txt, redir_type = OUTPUT_REDIR;
void CheckRedir(char cmd[])
{
    char *start = cmd;
    char *end = cmd + strlen(cmd) - 1;

    // ls -a -l >>   log.txt
    // ls -a -l
    while(start <= end)
    {
        // > >> <
        if(*start == '>')
        {
            if(*(start+1) == '>')
            {
                redir_type = APPEND_REDIR;
                // 追加重定向
                *start = '\0';
                start += 2;
                TrimSpace(start);
                filename = start;
                break;
            }
            else
            {
                // 输出重定向
                redir_type = OUPUT_REDIR;
                *start = '\0';
                start++;
                TrimSpace(start); // 移除空格
                filename = start;
                break;
            }
        }
        else if(*start == '<')
        {
            //输入重定向
            // cat <    log.txt
            redir_type = INPUT_REDIR;
            *start = '\0';
            start++;
            TrimSpace(start);
            filename = start;
            break;
        }
        else
        {
            start++;
        }
    }
}


  
std::string gethome()
{
  std::string home=getenv("HOME");
  return home.empty()?"NONE":home;

}
bool BuiltInCommandExec()
{
  std::string cmd =gargv[0];
  bool ret =false;
  if(cmd=="cd")
  {
    if(gargc ==2)
    {
      std::string target =gargv[1];
      if(target=="~")
      {
        ret =true;
        chdir(gethome().c_str());//chdir会改变进程的工作路径,但是环境变量中的工作目录不会自动更换，因为那个只是来自父进程的一个拷贝，需要手动更新，所以实现cd//的时候要手动获取最新的工作路径，系统中的cd就是这么实现的

      }
      else{
          ret =true;
          chdir(gargv[1]);

      }
    }
    else if(gargc==1)
    {
      ret=true;
      chdir(gethome().c_str());
    }
    else{}
  }
  return ret;
}
void ForkAndExec()
{
    pid_t id = fork();
    if(id < 0)
    {
        //for : XXXXX
        perror("fork"); // errno -> errstring
        return;
    }
    else if(id == 0)
    {
        if(redir_type == OUPUT_REDIR)
        {
            int output = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
            (void)output;
            dup2(output, 1);
        }
        else if(redir_type == INPUT_REDIR)
        {
            int input = open(filename.c_str(), O_RDONLY);
            (void)input;
            dup2(input, 0);
        }
        else if(redir_type == APPEND_REDIR)
        {
            int appendfd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND);
            (void)appendfd;
            dup2(appendfd, 1);
        }
        else{
            //Do Nothing
        }
        // 子进程打开文件了，也进行了重定向，execl*不影响历史打开的文件!
        //子进程
        execvp(gargv[0], gargv);
        exit(0);
    }
    else
    {
        //父进程
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
        }
    }
}
/*
void CheckRedir(char cmd[])
{
  char* start=cmd;
  char* end =cmd +strlen(cmd)-1;
  //ls - a -l >>log.txt
  //ls -a -l
  while(start <=end)
  {
    if(*start =='>')
    {
      if(*(start+1)=='>')
      {
        redir_type =APPEND_REDIR;
        *start='\0';
        start+=2;
        //TrimSpaece 去除字符串的空白字符
        TrimSpace(start);
        filename=start;
        break;
      }
      else
      {
         // 输出重定向
         redir_type = OUPUT_REDIR;
         *start = '\0';
         start++;
         TrimSpace(start); // 移除空格
         filename = start;
         break;
            }
    }
    else if(*start=='<')
    {
      //输入重定向
      // cat <    log.txt
            redir_type = INPUT_REDIR;
            *start = '\0';
            start++;
            TrimSpace(start);
            filename = start;
            break;
    }
    else{
      start++;
    }

  }

}*/

int main()
{
  char commandstr[1024];
  //shell是一个进程
  while(true)
  {
    //0.初始化
    InitGlobal();
    //1.输出命令行提示符
    PrintCommandPrompt();
    //2i.获取命令行的输入
   if (!GetCommandString(commandstr,SIZE))
   {
     continue;
   }
   //3.对字符串进行分割操作
   CheckRedir(commandstr);//对重定向
   ParseCommandString(commandstr);
   //4.检测命令，内建命令，要让shell自己执行！
  if(BuiltInCommandExec())
  {
    continue;
  }
   //5.执行命令，让子进程来执行
   ForkAndExec();
  }
  return 0;
}
//函数作用：
//chdir() 是C标准库中的函数（定义在 <unistd.h>），用于改变当前进程的工作目录。若成功返回0，失败返回-1（需检查errno获取具体错误）。
//参数解析：
//gargv[1]：假设gargv是全局变量，存储了程序的命令行参数（类似main函数的argv）。gargv[1]表示程序运行时传入的第一个参数（argv[0]通常是程序名）。
//功能示例：
//:若运行程序时输入 ./program /home/user，则gargv[1]为"/home/user"，代码会将当前目录切换至该路径。
